﻿using Jayden.Dll.Minecraft.Interfaces;
using Jayden.Dll.OpenGL.Enums;
using OpenTK;
using OpenTK.Graphics.OpenGL;

namespace Jayden.Dll.Minecraft.Model
{
	public class SimpleBlockModel : BlockModel
	{
		public class ImageId
		{
			public int X;
			public int Y;
			public int Id;
			public float TextureX;
			public float TextureY;
			public const float TextureSize = (float)(1 / 16.0);

			public ImageId(int id)
			{
				Id = id;
				X = id % 16;
				Y = id / 16;
				TextureX = (float)(X / 16.0);
				TextureY = (float)(Y / 16.0);
			}
			public ImageId(int x, int y)
			{
				Id = x + (y * 16);
				X = x;
				Y = y;
				TextureX = (float)(X / 16.0);
				TextureY = (float)(Y / 16.0);
			}
		}

		private int m_Id;
		private string m_Name;
		private ImageId m_DefaultImage;
		private ImageId[] m_FaceImages = new ImageId[6];
		private bool m_Opaque;
		private bool m_RenderFullBlock = false;

		public SimpleBlockModel(int id, string name, int terrainX, int terrainY, bool opaque = true, params ImageId[] faceImages)
		{
			m_Id = id;
			m_Name = name;
			m_DefaultImage = new ImageId(terrainX, terrainY);
			m_Opaque = opaque;

			for (int c = 0; c < m_FaceImages.Length && c < faceImages.Length; c++)
				m_FaceImages[c] = faceImages[c];

			BlockManager.Instance.Register(id, this);
		}

		public bool Opaque { get { return m_Opaque; } }

		public int Id { get { return m_Id; } }
		public string Name { get { return m_Name; } }

		public ImageId DefaultImage { get { return m_DefaultImage; } }

		public static void Normal(Vector3d a, Vector3d b, Vector3d c)
		{
			Vector3d dir = Vector3d.Cross(b - a, c - a);
			Vector3d norm = Vector3d.Normalize(dir);
			GL.Normal3(norm);
		}

		protected bool ShouldRenderBlock(Chunk chunk, int chunkX, int chunkY, int chunkZ)
		{
			return ShouldRenderFace(CubeFace.Back, chunk, chunkX, chunkY, chunkZ)
				|| ShouldRenderFace(CubeFace.Front, chunk, chunkX, chunkY, chunkZ)
				|| ShouldRenderFace(CubeFace.Left, chunk, chunkX, chunkY, chunkZ)
				|| ShouldRenderFace(CubeFace.Right, chunk, chunkX, chunkY, chunkZ)
				|| ShouldRenderFace(CubeFace.Top, chunk, chunkX, chunkY, chunkZ)
				|| ShouldRenderFace(CubeFace.Bottom, chunk, chunkX, chunkY, chunkZ);
		}

		protected bool ShouldRenderFace(CubeFace face, Chunk chunk, int chunkX, int chunkY, int chunkZ)
		{
			int checkX = -1, checkY = -1, checkZ = -1;
			bool noCheck = false;
			switch (face)
			{
				case CubeFace.Front:
					checkX = chunkX;
					checkY = chunkY;
					checkZ = chunkZ - 1;
					break;
				case CubeFace.Back:
					checkX = chunkX;
					checkY = chunkY;
					checkZ = chunkZ + 1;
					break;
				case CubeFace.Left:
					checkX = chunkX + 1;
					checkY = chunkY;
					checkZ = chunkZ;
					break;
				case CubeFace.Right:
					checkX = chunkX - 1;
					checkY = chunkY;
					checkZ = chunkZ;
					break;
				case CubeFace.Top:
					checkX = chunkX;
					checkY = chunkY + 1;
					checkZ = chunkZ;
					break;
				case CubeFace.Bottom:
					checkX = chunkX;
					checkY = chunkY - 1;
					checkZ = chunkZ;
					break;
				default:
					noCheck = true;
					break;
			}
			if (!noCheck)
			{
				int blockId = chunk[checkX, checkY, checkZ];
				BlockModel block = BlockManager.Instance[blockId];
				if (block != null && (block.Opaque || (!m_RenderFullBlock && block == this)))
					return false;
			}
			return true;
		}

		protected void RenderFace(CubeFace face, double baseX, double baseY)
		{
			switch (face)
			{
				case CubeFace.Front:
					RenderFrontFace(baseX, baseY);
					break;
				case CubeFace.Back:
					RenderBackFace(baseX, baseY);
					break;
				case CubeFace.Left:
					RenderLeftFace(baseX, baseY);
					break;
				case CubeFace.Right:
					RenderRightFace(baseX, baseY);
					break;
				case CubeFace.Top:
					RenderTopFace(baseX, baseY);
					break;
				case CubeFace.Bottom:
					RenderBottomFace(baseX, baseY);
					break;
				default:
					break;
			}
		}
		public SimpleBlockModel SetRenderFullBlock(bool renderFullBlock) { m_RenderFullBlock = renderFullBlock; return this; }
		public SimpleBlockModel SetFaceImage(CubeFace face, ImageId image)
		{
			m_FaceImages[(int)face] = image;
			return this;
		}
		public SimpleBlockModel SetFaceImage(CubeFace face, int x, int y) { return SetFaceImage(face, new ImageId(x, y)); }
		public SimpleBlockModel SetTopAndBottomImages(int x, int y)
		{
			ImageId image = new ImageId(x, y);
			return SetFaceImage(CubeFace.Top, image).SetFaceImage(CubeFace.Bottom, image);
		}

		protected virtual ImageId Texture(CubeFace face)
		{
			if (m_FaceImages[(int)face] != null)
				return m_FaceImages[(int)face];
			return m_DefaultImage;
		}
		protected virtual double TextureBaseX(CubeFace face) { return Texture(face).TextureX; }
		protected virtual double TextureBaseY(CubeFace face) { return Texture(face).TextureY; }

		private void RenderBottomFace(double baseX, double baseY)
		{
			Vector3d a, b, c, d;
			a = new Vector3d(-HalfSize, -HalfSize, HalfSize);
			b = new Vector3d(-HalfSize, -HalfSize, -HalfSize);
			c = new Vector3d(HalfSize, -HalfSize, -HalfSize);
			d = new Vector3d(HalfSize, -HalfSize, HalfSize);
			Normal(a, b, c);
			GL.TexCoord2(baseX + 1 / 16.0, baseY + 1 / 16.0);
			GL.Vertex3(a);
			GL.TexCoord2(baseX + 1 / 16.0, baseY);
			GL.Vertex3(b);
			GL.TexCoord2(baseX, baseY);
			GL.Vertex3(c);
			GL.TexCoord2(baseX, baseY + 1 / 16.0);
			GL.Vertex3(d);
		}

		private void RenderTopFace(double baseX, double baseY)
		{
			Vector3d a, b, c, d;
			a = new Vector3d(-HalfSize, HalfSize, HalfSize);
			b = new Vector3d(HalfSize, HalfSize, HalfSize);
			c = new Vector3d(HalfSize, HalfSize, -HalfSize);
			d = new Vector3d(-HalfSize, HalfSize, -HalfSize);
			Normal(a, b, c);
			GL.TexCoord2(baseX, baseY + 1 / 16.0);
			GL.Vertex3(a);
			GL.TexCoord2(baseX + 1 / 16.0, baseY + 1 / 16.0);
			GL.Vertex3(b);
			GL.TexCoord2(baseX + 1 / 16.0, baseY);
			GL.Vertex3(c);
			GL.TexCoord2(baseX, baseY);
			GL.Vertex3(d);
		}

		private void RenderRightFace(double baseX, double baseY)
		{
			Vector3d a, b, c, d;
			a = new Vector3d(-HalfSize, -HalfSize, HalfSize);
			b = new Vector3d(-HalfSize, HalfSize, HalfSize);
			c = new Vector3d(-HalfSize, HalfSize, -HalfSize);
			d = new Vector3d(-HalfSize, -HalfSize, -HalfSize);
			Normal(a, b, c);
			GL.TexCoord2(baseX + 1 / 16.0, baseY + 1 / 16.0);
			GL.Vertex3(a);
			GL.TexCoord2(baseX + 1 / 16.0, baseY);
			GL.Vertex3(b);
			GL.TexCoord2(baseX, baseY);
			GL.Vertex3(c);
			GL.TexCoord2(baseX, baseY + 1 / 16.0);
			GL.Vertex3(d);
		}

		private void RenderLeftFace(double baseX, double baseY)
		{
			Vector3d a, b, c, d;
			a = new Vector3d(HalfSize, -HalfSize, HalfSize);
			b = new Vector3d(HalfSize, -HalfSize, -HalfSize);
			c = new Vector3d(HalfSize, HalfSize, -HalfSize);
			d = new Vector3d(HalfSize, HalfSize, HalfSize);
			Normal(a, b, c);
			GL.TexCoord2(baseX, baseY + 1 / 16.0);
			GL.Vertex3(a);
			GL.TexCoord2(baseX + 1 / 16.0, baseY + 1 / 16.0);
			GL.Vertex3(b);
			GL.TexCoord2(baseX + 1 / 16.0, baseY);
			GL.Vertex3(c);
			GL.TexCoord2(baseX, baseY);
			GL.Vertex3(d);
		}

		private void RenderBackFace(double baseX, double baseY)
		{
			Vector3d a, b, c, d;
			a = new Vector3d(-HalfSize, -HalfSize, HalfSize);
			b = new Vector3d(HalfSize, -HalfSize, HalfSize);
			c = new Vector3d(HalfSize, HalfSize, HalfSize);
			d = new Vector3d(-HalfSize, HalfSize, HalfSize);
			Normal(a, b, c);
			GL.TexCoord2(baseX, baseY + 1 / 16.0);
			GL.Vertex3(a);
			GL.TexCoord2(baseX + 1 / 16.0, baseY + 1 / 16.0);
			GL.Vertex3(b);
			GL.TexCoord2(baseX + 1 / 16.0, baseY);
			GL.Vertex3(c);
			GL.TexCoord2(baseX, baseY);
			GL.Vertex3(d);
		}

		private void RenderFrontFace(double baseX, double baseY)
		{
			Vector3d a, b, c, d;
			a = new Vector3d(-HalfSize, -HalfSize, -HalfSize);
			b = new Vector3d(-HalfSize, HalfSize, -HalfSize);
			c = new Vector3d(HalfSize, HalfSize, -HalfSize);
			d = new Vector3d(HalfSize, -HalfSize, -HalfSize);
			Normal(a, b, c);
			GL.TexCoord2(baseX + 1 / 16.0, baseY + 1 / 16.0);
			GL.Vertex3(a);
			GL.TexCoord2(baseX + 1 / 16.0, baseY);
			GL.Vertex3(b);
			GL.TexCoord2(baseX, baseY);
			GL.Vertex3(c);
			GL.TexCoord2(baseX, baseY + 1 / 16.0);
			GL.Vertex3(d);
		}

		private const double HalfSize = 0.5;

		public void Render(Chunk chunk, int chunkX, int chunkY, int chunkZ)
		{
			if (!ShouldRenderBlock(chunk, chunkX, chunkY, chunkZ))
				return;
			GL.PushName(chunkX + chunkZ * 16 + chunkY * 256);

			GL.Begin(BeginMode.Quads);
			GL.Color3(1.0, 0.0, 0.0);
			if (ShouldRenderFace(CubeFace.Back, chunk, chunkX, chunkY, chunkZ))
				RenderBackFace(TextureBaseX(CubeFace.Back), TextureBaseY(CubeFace.Back));

			if (ShouldRenderFace(CubeFace.Front, chunk, chunkX, chunkY, chunkZ))
				RenderFrontFace(TextureBaseX(CubeFace.Front), TextureBaseY(CubeFace.Front));

			if (ShouldRenderFace(CubeFace.Left, chunk, chunkX, chunkY, chunkZ))
				RenderLeftFace(TextureBaseX(CubeFace.Left), TextureBaseY(CubeFace.Left));

			if (ShouldRenderFace(CubeFace.Right, chunk, chunkX, chunkY, chunkZ))
				RenderRightFace(TextureBaseX(CubeFace.Right), TextureBaseY(CubeFace.Right));

			if (ShouldRenderFace(CubeFace.Top, chunk, chunkX, chunkY, chunkZ))
				RenderTopFace(TextureBaseX(CubeFace.Top), TextureBaseY(CubeFace.Top));

			if (ShouldRenderFace(CubeFace.Bottom, chunk, chunkX, chunkY, chunkZ))
				RenderBottomFace(TextureBaseX(CubeFace.Bottom), TextureBaseY(CubeFace.Bottom));

			GL.End();

			GL.PopName();

		}

	}

}
